/**
 * $Id: JavaMailSenderImpl.java 45 2007-01-14 19:21:21Z lagnada $
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 
 * 2. The name "Antonio W. Lagnada" must not be used to endorse or promote
 * products derived from this software without prior written permission. For
 * written permission, please contact tony@lagnada.com
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ANTONIO W.
 * LAGNADA OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *  
 */
package com.demo.mail;

import static org.springframework.util.Assert.*;

import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.mail.AuthenticationFailedException;
import javax.mail.MessagingException;
import javax.mail.Transport;
import javax.mail.internet.MimeMessage;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.annotation.Required;
import org.springframework.mail.MailAuthenticationException;
import org.springframework.mail.MailException;
import org.springframework.mail.MailSendException;
import org.springframework.util.Assert;

/**
 * JavaMailSenderImpl (with secure smtp support)
 * <p>
 * This is an example Spring configuration:
 * </p>
 * <code>
 * <!-- Make sure RequiredAnnotationBeanPostProcessor is declared -->
 * <bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor" />
 * 
 * <bean id="mailProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
 *      <property name="location" value="classpath:mail.properties" />
 * </bean>
 * 
 * <bean id="javaMailSender" class="com.demo.mail.JavaMailSenderImpl" init-method="init">
 *      <property name="mailProperties" ref="mailProperties" />
 * </bean>
 * </code>
 */
public class JavaMailSenderImpl
        extends
            org.springframework.mail.javamail.JavaMailSenderImpl {

    private final static Log log = LogFactory.getLog(JavaMailSenderImpl.class);

    public final static String HOST_FORMAT = "mail.%s.host";

    public final static String PORT_FORMAT = "mail.%s.port";

    public final static String USER_FORMAT = "mail.%s.user";

    public final static String AUTH_FORMAT = "mail.%s.auth";

    public final static String SMTP_IS_SECURE = "smtp.isSecure";

    public final static String SMTP_REQUIRES_AUTH = "smtp.requiresAuthentication";

    public final static String SMTP_HOST = "smtp.host";

    public final static String SMTP_USERNAME = "smtp.username";

    public final static String SMTP_PASSWORD = "smtp.password";

    public final static String SMTP_PORT = "smtp.port";

    public final static String MAIL_CHARSET = "mail.charset";

    private final static String REQUIRED_MESSAGE = "The object [%s] is required.";

    //

    protected Properties m_mailProperties;

    //

    public JavaMailSenderImpl() {
        super();
    }

    public void init() {
        configureMailProperties();
        setJavaMailProperties(m_mailProperties);
    }

    public void configureMailProperties() {
        String propertyMessage = "Server Property %s";

        Boolean requiresAuth = getBooleanProperty(SMTP_REQUIRES_AUTH);
        assertRequired(requiresAuth, String.format(propertyMessage,
                SMTP_REQUIRES_AUTH));

        Boolean isSecureSMTP = getBooleanProperty(SMTP_IS_SECURE);
        assertRequired(isSecureSMTP, String.format(propertyMessage,
                SMTP_IS_SECURE));

        String host = getProperty(SMTP_HOST);
        assertRequired(host, "Server Property " + SMTP_HOST);

        Integer port = getIntegerProperty(SMTP_PORT);
        assertRequired(port, String.format(propertyMessage, SMTP_PORT));

        String username = getProperty(SMTP_USERNAME);
        assertRequired(username, String.format(propertyMessage, SMTP_USERNAME));

        String password = getProperty(SMTP_PASSWORD);
        assertRequired(password, String.format(propertyMessage, SMTP_PASSWORD));

        if (log.isDebugEnabled()) {
            m_mailProperties.setProperty("mail.debug", "true");
            m_mailProperties.setProperty("mail.smtp.debug", "true");
        }

        String protocol = "smtp";
        if (isSecureSMTP) {
            protocol = "smtps";
            setProtocol(protocol);
            if (log.isDebugEnabled()) {
                m_mailProperties.setProperty("mail.smtps.debug", "true");
            }
        }
        String hostPropertyName = String.format(HOST_FORMAT, protocol);
        String portPropertyName = String.format(PORT_FORMAT, protocol);
        String authPropertyName = String.format(AUTH_FORMAT, protocol);

        // WARNING: The Boolean(s) and Integer(s) have to be "string" values
        // otherwise java mail send won't recognize the property value.
        // Don't use properties.put(object, object) here.
        m_mailProperties.setProperty(authPropertyName, requiresAuth.toString());
        m_mailProperties.setProperty(hostPropertyName, host);
        m_mailProperties.setProperty(portPropertyName, port.toString());

        setHost(host);
        setPort(port);
        setUsername(username);
        setPassword(password);

        if (log.isDebugEnabled()) {
            log.info("Mail Properties ***************************");
            for (Object propKey : m_mailProperties.keySet()) {
                log.info(propKey + "=[" + m_mailProperties.get(propKey) + "]");
            }
        }
    }

    @Override
    protected void doSend(MimeMessage[] mimeMessages, Object[] originalMessages)
            throws MailException {
        configureMailProperties();

        Map<Object, Exception> failedMessages = new HashMap<Object, Exception>();
        Transport transport = null;
        try {
            transport = getSession().getTransport(getProtocol());
            transport.connect(getHost(), getPort(), getUsername(),
                    getPassword());
        } catch (AuthenticationFailedException ex) {
            throw new MailAuthenticationException(ex);
        } catch (MessagingException ex) {
            throw new MailSendException("Mail server connection failed", ex);
        }

        if (transport.isConnected()) {
            try {
                for (int i = 0; i < mimeMessages.length; i++) {
                    MimeMessage mimeMessage = mimeMessages[i];
                    try {
                        if (mimeMessage.getSentDate() == null) {
                            mimeMessage.setSentDate(new Date());
                        }
                        mimeMessage.saveChanges();
                        transport.sendMessage(mimeMessage, mimeMessage
                                .getAllRecipients());
                    } catch (MessagingException ex) {
                        Object original = (originalMessages != null
                                ? originalMessages[i]
                                : mimeMessage);
                        failedMessages.setProperty(original, ex);
                    }
                }
            } finally {
                try {
                    transport.close();
                } catch (MessagingException e) {
                    throw (new MailSendException("Error sending mail.", e));
                }
            }
        }

        if (!failedMessages.isEmpty()) {
            throw new MailSendException(failedMessages);
        }
    }

    public static void assertRequired(Object obj, String objectName) {
        Assert.notNull(obj, String.format(REQUIRED_MESSAGE, objectName));
    }

    public String getProperty(String propertyName) {
        assertRequired(propertyName, "PropertyName");
        return StringUtils.trimToEmpty(m_mailProperties
                .getProperty(propertyName));
    }

    public Boolean getBooleanProperty(String propertyName) {
        assertRequired(propertyName, "PropertyName");
        return new Boolean(getProperty(propertyName));
    }

    public Integer getIntegerProperty(String propertyName) {
        assertRequired(propertyName, "PropertyName");
        return NumberUtils.createInteger(getProperty(propertyName));
    }

    public Long getLongProperty(String propertyName) {
        assertRequired(propertyName, "PropertyName");
        return NumberUtils.createLong(getProperty(propertyName));
    }

    @Required
    public void setMailProperties(Properties mailProperties) {
        m_mailProperties = mailProperties;
    }

}
